﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Xml;
using Aleator.Core.Techniques;

namespace Aleator.Core
{
    /// <summary>
    /// Phrase base class
    /// </summary>
    public class ConcretePhrase : Phrase
    {
        #region Members
        #pragma warning disable 1591

        //Progression
        protected static NoteFactory m_notefactory = new ConcreteNoteFactory();

        protected ConcreteProgression m_progression;

        protected List<ConcreteNote> m_homeOctaveNotes;

        //Note related members
        protected List<ConcreteNote> m_notes;
        protected int m_noteTotal;
        protected int m_noteMod;

        //Distribution
        protected static ContinuousUniformDistribution m_continuousMeasure = new ContinuousUniformDistribution();
        protected Distribution m_noteDistribution;
        protected static RayleighDistribution m_rayleigh = new RayleighDistribution();
        protected static DiscreteUniformDistribution m_discrete = new DiscreteUniformDistribution();
        protected static ContinuousUniformDistribution m_continuous = new ContinuousUniformDistribution();
        protected static NormalDistribution m_normal = new NormalDistribution();

        //Miscellenous
        protected int m_resolution;

        protected int m_location = 0;
        protected int m_measure = 0;

        protected int m_noteNum;
        protected int m_noteOnMessageNumber;
        protected int m_noteOffMessageNumber;

        protected int m_clusterCount;
        protected int m_totalBeats;

        protected double m_mu;
        protected double m_sigma = 2.0;
        protected int m_spacer;

        protected Technique.TechniqueType m_techniqueType;

        #pragma warning restore 1591
        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        public ConcretePhrase()
        {
        }

        /// <summary>
        /// Main constructor
        /// </summary>
        /// <param name="progression">Progression this phrase is associated with</param>
        public ConcretePhrase(ConcreteProgression progression)
        {
            Notes = new List<ConcreteNote>();

            m_noteDistribution = (Distribution)Enum.ToObject(typeof(Distribution), Session.RndGenerator.Next(2));

            SetTotals();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Progression property
        /// </summary>
        public override ConcreteProgression Progression
        {
            get
            {
                return m_progression;
            }
            set
            {
                m_progression = value;
            }
        }

        /// <summary>
        /// Notes in this phrase
        /// </summary>
        public override List<ConcreteNote> Notes
        {
            get
            {
                return  m_notes;
            }
            set
            {
                m_notes = value;
            }
        }

        /// <summary>
        /// Indicator of how notes are distributed across this phrase (loose, tight or rigid)
        /// </summary>
        public override Distribution NoteDistribution
        {
            get
            {
                return m_noteDistribution;
            }
            set
            {
                m_noteDistribution = value;
            }
        }

        /// <summary>
        /// Number of possible note plays per quarter note.
        /// </summary>
        public override int Resolution 
        {
            get
            {
                return m_resolution;
            }
            set
            {
                if ((value % 2 == 0 && value <= 8) || value == 1)
                    m_resolution = value;
                else
                    Debug.WriteLine("Invalid quantize value.");
            }
        }

        /// <summary>
        /// Number of possible note plays in a measure
        /// </summary>
        public override int PlayableNotesInMeasure
        {
            get
            {
                return m_progression.Numerator * m_resolution;
            }
        }

        /// <summary>
        /// Integer representing the number of times this phrase will repeate
        /// </summary>
        public override int Multiplier
        {
            get
            {
                return m_progression.TotalBeats / m_totalBeats;
            }
        }

        /// <summary>
        /// Place holder for chord interval for pitch selction during note additions
        /// </summary>
        public override string ChordInterval { get; set; }

        /// <summary>
        /// The type of technique used to generate this phrase
        /// </summary>
        public override Technique.TechniqueType TechniqueType
        {
            get
            {
                return m_techniqueType;
            }
            set
            {
                m_techniqueType = value;
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Set distribution object parameters
        /// </summary>
        /// <param name="sigma">Standard deviation</param>
        protected override void SetTotals(double sigma = 2.0)
        {
            m_sigma = sigma;
            m_mu = m_progression.TotalBeats / 2;
        }

        /// <summary>
        /// Get note list totals
        /// </summary>
        /// <param name="mu">Mean for normal distribution</param>
        /// <param name="sigma">Standard deviation for normal distribution</param>
        /// <returns></returns>
        protected override int GetTotal(double mu, double sigma)
        {
            //Use normal distribution to calculate drum note totals
            m_normal.Mu = mu;
            m_normal.Sigma = sigma;

            double next = 0.0;
            int intnext = 0;

            while (Convert.ToInt32(next) <= 0)
                next = m_normal.NextDouble();

            intnext = (int)next;

            //This ensures that there is at least one of each in every measure. Will probably need to change this later.
            if (intnext < m_progression.TotalMeasures)
                return m_progression.TotalMeasures;
            else
                return intnext;
        }

        /// <summary>
        /// Determine the location of a note
        /// </summary>
        protected override void GetBeat()
        {
            //Pick a distribution generator based on phrase XML
            if (Progression.DrumPhrase.PhraseRhythm.Distribution == "Uniform")
            {
                m_location = Math.Min((int)Math.Round(m_continuous.NextDouble()), PlayableNotesInMeasure - 1);
            }
            else
            {
                m_location = Math.Min((int)Math.Round((m_rayleigh.NextDouble() / 10) * PlayableNotesInMeasure),
                    PlayableNotesInMeasure - 1);

                //If this phrase is wieghted to the back, we have to take flip the beat value
                if (Progression.DrumPhrase.PhraseRhythm.Distribution == "Back")
                {
                    m_location = PlayableNotesInMeasure - m_location - 1;
                }
            }
        }

        /// <summary>
        /// Transfer another phrase's notes to this one and truncate/fill out as needed
        /// </summary>
        /// <param name="p">The phrase we are copying from</param>
        protected override void CopyToPhrase(ConcretePhrase p)
        {
            //p is the phrase we are using to build from
            m_notes = new List<ConcreteNote>();

            //Short copy
            if (this.Progression.TotalMeasures < p.Progression.TotalMeasures)
                this.Notes.AddRange(p.Notes.Where(n => n.Measure < this.Progression.TotalMeasures));
            //Long copy
            else if (this.Progression.TotalMeasures > p.Progression.TotalMeasures)
            {
                int additional = this.Progression.TotalMeasures - p.Progression.TotalMeasures;
                int measureadd = p.Progression.TotalMeasures;

                ConcreteNote newnote = null;
                List<ConcreteNote> newnotes = new List<ConcreteNote>();

                foreach (ConcreteNote n in p.Notes.Where(n => n.Measure < additional))
                {
                    if (this.GetType() == typeof(DrumPhrase))
                        newnote = (ConcreteNote)m_notefactory.GetNote(n.Location, n.Measure + measureadd, n.NoteID, n.MessageOnID, n.MessageOffID, this);
                    else
                    {
                        this.Resolution = p.Resolution;
                        newnote = (ConcreteNote)m_notefactory.GetNote(n.Location, n.Measure + measureadd, n, n.MessageOnID, n.MessageOffID, this);
                    }

                    newnote.OffMeasure = n.OffMeasure + measureadd;
                    newnote.OffLocation = n.OffLocation;

                    newnotes.Add(newnote);
                }
                
                this.Notes.AddRange(p.Notes);
                this.Notes.AddRange(newnotes);
            }
            //Straight copy
            else
                this.Notes.AddRange(p.Notes);

            //this.Notes.RemoveAll(n => n.OffMeasure > m_progression.TotalMeasures - 1);
            //this.Notes.RemoveAll(n => n.OffMeasure == n.Measure && n.OffLocation == n.Location);
        }

        /// <summary>
        /// Generate not implemented in base class yet
        /// </summary>
        /// <param name="resolution"></param>
        protected override void Generate(int resolution)
        {
            m_resolution = resolution;
        }

        /// <summary>
        /// Mute all notes
        /// </summary>
        public override void Mute()
        {
            foreach (Note n in this.Notes)
                n.NoteOnEvent.Data[2] = 0;
        }

        /// <summary>
        /// Unmute all notes
        /// </summary>
        public override void UnMute()
        {
            foreach (Note n in this.Notes)
                n.NoteOnEvent.Data[2] = 100;
        }

        #endregion
    }
}
